* 3. Drop a container anywhere on the page: *
* * OPTIONAL ATTRIBUTES (on the container
) * data-speed="1" Animation speed multiplier (0.1–3) * data-density="28" Number of rising particles (4–80) * data-controls="false" Hide the on-page Tweaks panel (default: shown) * * The illustration scales to fill its container while preserving its 1:1 * aspect ratio. Give the container an explicit width and height (or * aspect-ratio) — the widget does the rest. Renders inside a Shadow DOM, so * it cannot conflict with Webflow's site-wide CSS. */ (function(){ function mount(host){ if (host.__koreMounted) return; host.__koreMounted = true; const root = host.attachShadow({mode:'open'}); root.innerHTML = "
\n
\n\n \n \"\"\n\n \n
\n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n
\n\n \n
\n
\n
\n
\n\n
\n
\n\n
\n

Tweaks

\n
\n \n \n
\n
\n \n \n
\n
Higher density = more rising elements.
\n
"; // Re-implement getElementById against the shadow root. const $ = (id) => root.getElementById(id); const stage = $('stage'); // Fit the 777.6 design canvas to the host element's box. function fit(){ const W = 777.6, H = 777.6; const rect = host.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) return; const s = Math.min(rect.width / W, rect.height / H); const offX = (rect.width - W*s) / 2; const offY = (rect.height - H*s) / 2; stage.style.transformOrigin = '0 0'; stage.style.left = offX + 'px'; stage.style.top = offY + 'px'; stage.style.transform = 'scale(' + s + ')'; } requestAnimationFrame(fit); new ResizeObserver(fit).observe(host); window.addEventListener('load', fit); // === Particle animation (adapted from the original inline script) === const COLORS = [ 'rgb(50,199,202)','rgb(83,241,246)','rgb(117,244,248)', 'rgb(176,249,251)','rgb(6,97,111)','rgb(3,120,136)','rgb(55,199,202)' ]; const colA = $('colA'), colB = $('colB'); const LANES_A = [4,28,56,86,118,144]; const LANES_B = [10,36,64,92,120,150]; const COL_A_H = 459.807, COL_B_H = 465.377; const state = { speed:1, density:28, particles:[], lastT: performance.now() }; const rand = (a,b) => a + Math.random()*(b-a); const pick = a => a[Math.floor(Math.random()*a.length)]; function spawn(){ const useA = Math.random() < 0.5; const parent = useA ? colA : colB; const lanes = useA ? LANES_A : LANES_B; const colH = useA ? COL_A_H : COL_B_H; const lane = pick(lanes); const height = rand(14,70); const duration = rand(2.4,5.6); const startDelay = rand(0,duration); const el = document.createElement('div'); el.className = 'particle'; el.style.left = lane + 'px'; el.style.height = height + 'px'; el.style.background = pick(COLORS); parent.appendChild(el); return { el, column: useA?'A':'B', colH, height, duration, progress: -startDelay/duration }; } function setDensity(n){ state.density = n; while (state.particles.length < n) state.particles.push(spawn()); while (state.particles.length > n) state.particles.pop().el.remove(); } function recycle(p){ const useA = Math.random() < 0.5; const parent = useA ? colA : colB; const lanes = useA ? LANES_A : LANES_B; const colH = useA ? COL_A_H : COL_B_H; if (p.column !== (useA?'A':'B')) { parent.appendChild(p.el); p.column = useA?'A':'B'; } p.colH = colH; p.height = rand(14,70); p.duration = rand(2.4,5.6); p.progress = 0; p.el.style.left = pick(lanes) + 'px'; p.el.style.height = p.height + 'px'; p.el.style.background = pick(COLORS); } function tick(now){ const dt = Math.min(0.05, (now - state.lastT)/1000); state.lastT = now; for (const p of state.particles){ p.progress += (dt * state.speed) / p.duration; if (p.progress >= 1){ recycle(p); continue; } if (p.progress < 0){ p.el.style.visibility='hidden'; continue; } p.el.style.visibility='visible'; const t = p.progress; const y = p.colH - t * (p.colH + p.height); let fade = 1; if (t < 0.08) fade = t / 0.08; else if (t > 0.85) fade = (1 - t) / 0.15; p.el.style.transform = 'translateY(' + y.toFixed(2) + 'px)'; p.el.style.opacity = (fade * 0.95).toFixed(2); } requestAnimationFrame(tick); } // === Tweaks panel wiring === const speedEl = $('speed'), speedV = $('speedV'); const densityEl = $('density'), densityV = $('densityV'); speedEl.addEventListener('input', () => { state.speed = parseFloat(speedEl.value); speedV.textContent = state.speed.toFixed(2) + '×'; }); densityEl.addEventListener('input', () => { const n = parseInt(densityEl.value, 10); densityV.textContent = n; setDensity(n); }); // Read optional config from data-attrs const initSpeed = parseFloat(host.getAttribute('data-speed') || '1'); const initDensity = parseInt(host.getAttribute('data-density') || '28', 10); const showPanel = host.getAttribute('data-controls') !== 'false'; if (!showPanel) { const panel = root.querySelector('.panel'); if (panel) panel.style.display = 'none'; } speedEl.value = initSpeed; state.speed = initSpeed; speedV.textContent = initSpeed.toFixed(2) + '×'; densityEl.value = initDensity; densityV.textContent = initDensity; setDensity(initDensity); requestAnimationFrame(t => { state.lastT = t; tick(t); }); } function init(){ const targets = document.querySelectorAll('[data-kore-illustration]'); targets.forEach(mount); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } // Expose a manual mount in case markup is added dynamically. window.KoreIllustration = { mount, init }; })();